#include "geo.h"
#include "bmp.h"
#include "models.h"
#include <cmath>
#include <cstring>
#include <vector>
#include <ctime>

const bool SOFT_SHADOW=true;
const int SOFT_SHADOW_W=3;
const int SOFT_SHADOW_RAND_N=2000;

class Timer {
	clock_t start;
public:
	Timer() {
		start=clock();
	}
	double restart() {
		double ret=tot();
		start=clock();
		return ret;
	}
	double tot() {
		clock_t t=clock()-start;
		return (double)t/(double)CLOCKS_PER_SEC;
	}
};

// Phong reflection model
double c_func(const Point &p, const Point &normal, const Point &viewer, const Point &light) {
	Point L=light-p;  L.normalize();
	Point N=normal;   N.normalize();
	Point R=N*dot(L, N)*2-L;  R.normalize();
	Point V=viewer-p; V.normalize();
	double i=0.;
	i+=0.1;
	i+=max(0., dot(L, N))*0.7;
	i+=pow(dot(R, V), 100)*0.7;
	i=min(i, 1.);
	return i;
}

bool check_shadow(const Model &model, Point p, Point light) {
	return model.tree.check_cross(Ray(p, light-p));
}

double get_light_ratio(const Model &model, Point p, const Light &light) {
	if(!SOFT_SHADOW || light.r<=0)
		return check_shadow(model, p, light.p) ? 0. : 1.;
	int cnt=0, tot=0;
	for(int tx=-SOFT_SHADOW_W; tx<=SOFT_SHADOW_W; ++tx)
		for(int ty=-SOFT_SHADOW_W; ty<=SOFT_SHADOW_W; ++ty) {
			Point l=light.get((double)tx/(double)SOFT_SHADOW_W, (double)ty/(double)SOFT_SHADOW_W);
			if(!check_shadow(model, p, l))
				++cnt;
			++tot;
		}
	if(cnt==0 || cnt==tot)
		return (double)cnt/(double)tot;
	for(int tt=0; tt<SOFT_SHADOW_RAND_N; ++tt) {
		Point l=light.get();
		if(!check_shadow(model, p, l))
			++cnt;
		++tot;
	}
	return (double)cnt/(double)tot;	
}

Color ray_tracing(const Model &model, Ray ray) {
	Color ans=Color(0.1, 0, 0.1);
	double t;
	Patch patch;
	double alpha=0, beta=0;
	
	if(model.tree.get_cross(ray, &patch, &t, &alpha, &beta)) {
		Point p=patch.get_point(alpha, beta);

		Point normal=patch.get_normal(alpha, beta);
		ans=Color();		
		for(size_t i=0; i<model.lights.size(); ++i) {
			double li=get_light_ratio(model, p, model.lights[i]);
			double c=c_func(p, normal, ray.s, model.lights[i].p);
			ans.merge(patch.c*model.lights[i].c*c*li);
		}
	}
	return ans;
}

Ray get_screen_ray(double h, double w, double i, double j) {
	Ray ray;
	ray.s=Point(0, -10, 0);
	ray.dir=Point(j-w/2., 4000, h/2.-i);
	return ray;
}

bool need_antialiasing(const vector<vector<Color> > &colors, int i, int j) {
	/*
	const int dx[4]={0, 1, 0, -1};
	const int dy[4]={1, 0, -1, 0};
	for(int k=0; k<4; ++k) {
		int i0=i+dx[k];
		int j0=j+dy[k];
		if(i0<0 || i0>=(int)colors.size()) continue;
		if(j0<0 || j0>=(int)colors[i0].size()) continue;
		
	}
	*/
	return false;
}

void run(const Model &model, Bmp &bmp) {
	vector<vector<Color> > colors;
	vector<vector<bool> > bs;
	vector<pair<int, int> > tasks;
	colors.resize(bmp.h);
	bs.resize(bmp.h);
	for(int i=0; i<bmp.h; ++i) {
		colors[i].resize(bmp.w);
		bs[i].resize(bmp.w);
		for(int j=0; j<bmp.w; ++j)
			tasks.push_back(make_pair(i, j));
	}
	for(size_t i=0; i<tasks.size(); ++i) {
		size_t j=rand()&((1<<15)-1);
		j=(j<<15)|(rand()&((1<<15)-1));
		j=j%(i+1);
		swap(tasks[i], tasks[j]);
	}
	
	for(size_t k=0; k<tasks.size(); ++k) {
	//for(size_t k=853000; k<855000; ++k) {
		if((k+1)%100==0) printf("k=%d / %d\n", k+1, tasks.size());
		if(k%1000==0)	
			bmp.print_to_file("a.bmp");
		int i=tasks[k].first;
		int j=tasks[k].second;
		Ray ray=get_screen_ray(bmp.h, bmp.w, i, j);
		//ray.dir.normalize();
		colors[i][j]=ray_tracing(model, ray);
		bmp.pixels[i][j]=colors[i][j].to_pixel();
	}
	
	// antialiasing
	for(int i=0; i<bmp.h; ++i)
		for(int j=0; j<bmp.w; ++j)
			bs[i][j]=need_antialiasing(colors, i, j);
	for(size_t k=0; k<tasks.size(); ++k) {
		int i=tasks[k].first;
		int j=tasks[k].second;
		if(bs[i][j]) {
			Color c1=ray_tracing(model, get_screen_ray(bmp.h, bmp.w, i-.3, j-.3));
			Color c2=ray_tracing(model, get_screen_ray(bmp.h, bmp.w, i-.3, j+.3));
			Color c3=ray_tracing(model, get_screen_ray(bmp.h, bmp.w, i+.3, j-.3));
			Color c4=ray_tracing(model, get_screen_ray(bmp.h, bmp.w, i+.3, j+.3));
			colors[i][j]=(colors[i][j]+c1+c2+c3+c4)*.2;
			bmp.pixels[i][j]=colors[i][j].to_pixel();
		}
	}
}

int main() {
	double t_load, t_build, t_render;
	//freopen("log.txt", "w", stdout);
	
	Bmp bmp(800, 1000);
	//Bmp bmp(400, 500);
	Timer timer;
	//*
	vector<Patch> patches=Models::load_obj("data/stanford_dragon/dragon.obj", Color(.95, .70, .0));
	Models::add_bottom(patches, Color(.7, .7, .7), 2, -0.3);
	
	vector<Light> lights;
	Color lc=Color(1, 1, 1);
	lights.push_back(Light(Point(2, -10, 12), 3, lc*0.8));
	lights.push_back(Light(Point(-2, -5, -.3), 0, lc*0.2));
	/*
	lights.push_back(Light(Point(-5, -5, -0.3), 0, lc*0.025));
	lights.push_back(Light(Point(+0, -5, -0.3), 0, lc*0.05));
	lights.push_back(Light(Point(+5, -5, -0.3), 0, lc*0.025));
	*/
	
	Models::rotate(patches, lights, Point(0,0,1), PI/12);
	Models::rotate(patches, lights, Point(1,0,0), PI/12);
	//*/
	//vector<Patch> patches=Models::random_sphere(5000, Color(0.8, .99, .9));
	//vector<Patch> patches=Models::earth_sphere(10, 20, Color(0.8, .99, .9));
	
	t_load=timer.restart();
	
	Model model(patches, lights);
	/*
	model.lights.push_back(Light(Point(-11, -9, 15), bc));
	model.lights.push_back(Light(Point(-9, -11, 15), bc));
	model.lights.push_back(Light(Point(-11, -11, 15), bc));
	model.lights.push_back(Light(Point(-9, -9, 15), bc));
	//*/
	//model.lights.push_back(Point(10, 10, -5));
	t_build=timer.restart();

	run(model, bmp);
	t_render=timer.restart();

	bmp.print_to_file("a.bmp");
	
	printf("## Finished.\n");
	printf("## Model:\n");
	printf("##     patches    = %d\n", patches.size());
	printf("## Time:\n");
	printf("##     load model = %.2lf s\n", t_load);
	printf("##     build tree = %.2lf s\n", t_build);
	printf("##     render     = %.2lf s\n", t_render);

	return 0;
}
